/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2016 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * The Plugin Manager is responsible for the loading, running and unloading of Phaser Plugins.
 *
 * @class Phaser.PluginManager
 * @constructor
 * @param {Phaser.Game} game - A reference to the currently running game.
 */
Phaser.PluginManager = function (game)
{
    /**
     * @property {Phaser.Game} game - A reference to the currently running game.
     */
    this.game = game;

    /**
     * @property {Phaser.Plugin[]} plugins - An array of all the plugins being managed by this PluginManager.
     */
    this.plugins = [];

    /**
     * @property {number} _len - Internal cache var.
     * @private
     */
    this._len = 0;

    /**
     * @property {number} _i - Internal cache var.
     * @private
     */
    this._i = 0;
};

Phaser.PluginManager.prototype = {

    /**
     * Add a new Plugin into the PluginManager.
     * The Plugin must have 2 properties: game and parent. Plugin.game is set to the game reference the PluginManager uses, and parent is set to the PluginManager.
     *
     * @method Phaser.PluginManager#add
     * @param {object|Phaser.Plugin} plugin - The Plugin to add into the PluginManager. This can be a function or an existing object.
     * @param {...*} parameter - Additional arguments that will be passed to the Plugin.init method.
     * @return {Phaser.Plugin} The Plugin that was added to the manager.
     */
    add: function (plugin)
    {
        var args = Array.prototype.slice.call(arguments, 1);
        var result = false;

        //  Prototype?
        if (typeof plugin === 'function')
        {
            plugin = new plugin(this.game, this);
        }
        else
        {
            plugin.game = this.game;
            plugin.parent = this;
        }

        //  Check for methods now to avoid having to do this every loop
        if (typeof plugin.preUpdate === 'function')
        {
            plugin.hasPreUpdate = true;
            result = true;
        }

        if (typeof plugin.update === 'function')
        {
            plugin.hasUpdate = true;
            result = true;
        }

        if (typeof plugin.postUpdate === 'function')
        {
            plugin.hasPostUpdate = true;
            result = true;
        }

        if (typeof plugin.render === 'function')
        {
            plugin.hasRender = true;
            result = true;
        }

        if (typeof plugin.postRender === 'function')
        {
            plugin.hasPostRender = true;
            result = true;
        }

        //  The plugin must have at least one of the above functions to be added to the PluginManager.
        if (result)
        {
            if (plugin.hasPreUpdate || plugin.hasUpdate || plugin.hasPostUpdate)
            {
                plugin.active = true;
            }

            if (plugin.hasRender || plugin.hasPostRender)
            {
                plugin.visible = true;
            }

            this._len = this.plugins.push(plugin);

            // Allows plugins to run potentially destructive code outside of the constructor, and only if being added to the PluginManager
            if (typeof plugin.init === 'function')
            {
                plugin.init.apply(plugin, args);
            }

            return plugin;
        }
        else
        {
            return null;
        }
    },

    /**
     * Remove a Plugin from the PluginManager. It calls Plugin.destroy on the plugin before removing it from the manager.
     *
     * @method Phaser.PluginManager#remove
     * @param {Phaser.Plugin} plugin - The plugin to be removed.
     * @param {boolean} [destroy=true] - Call destroy on the plugin that is removed?
     */
    remove: function (plugin, destroy)
    {
        if (destroy === undefined) { destroy = true; }

        this._i = this._len;

        while (this._i--)
        {
            if (this.plugins[this._i] === plugin)
            {
                if (destroy)
                {
                    plugin.destroy();
                }

                this.plugins.splice(this._i, 1);
                this._len--;
                return;
            }
        }
    },

    /**
     * Remove all Plugins from the PluginManager. It calls Plugin.destroy on every plugin before removing it from the manager.
     *
     * @method Phaser.PluginManager#removeAll
     */
    removeAll: function ()
    {
        this._i = this._len;

        while (this._i--)
        {
            this.plugins[this._i].destroy();
        }

        this.plugins.length = 0;
        this._len = 0;
    },

    /**
     * Pre-update is called at the very start of the update cycle, before any other subsystems have been updated (including Physics).
     * It only calls plugins who have active=true.
     *
     * @method Phaser.PluginManager#preUpdate
     */
    preUpdate: function ()
    {
        this._i = this._len;

        while (this._i--)
        {
            if (this.plugins[this._i].active && this.plugins[this._i].hasPreUpdate)
            {
                this.plugins[this._i].preUpdate();
            }
        }
    },

    /**
     * Update is called after all the core subsystems (Input, Tweens, Sound, etc) and the State have updated, but before the render.
     * It only calls plugins who have active=true.
     *
     * @method Phaser.PluginManager#update
     */
    update: function ()
    {
        this._i = this._len;

        while (this._i--)
        {
            if (this.plugins[this._i].active && this.plugins[this._i].hasUpdate)
            {
                this.plugins[this._i].update();
            }
        }
    },

    /**
     * PostUpdate is the last thing to be called before the world render.
     * In particular, it is called after the world postUpdate, which means the camera has been adjusted.
     * It only calls plugins who have active=true.
     *
     * @method Phaser.PluginManager#postUpdate
     */
    postUpdate: function ()
    {
        this._i = this._len;

        while (this._i--)
        {
            if (this.plugins[this._i].active && this.plugins[this._i].hasPostUpdate)
            {
                this.plugins[this._i].postUpdate();
            }
        }
    },

    /**
     * Render is called right after the Game Renderer completes, but before the State.render.
     * It only calls plugins who have visible=true.
     *
     * @method Phaser.PluginManager#render
     */
    render: function ()
    {
        this._i = this._len;

        while (this._i--)
        {
            if (this.plugins[this._i].visible && this.plugins[this._i].hasRender)
            {
                this.plugins[this._i].render();
            }
        }
    },

    /**
     * Post-render is called after the Game Renderer and State.render have run.
     * It only calls plugins who have visible=true.
     *
     * @method Phaser.PluginManager#postRender
     */
    postRender: function ()
    {
        this._i = this._len;

        while (this._i--)
        {
            if (this.plugins[this._i].visible && this.plugins[this._i].hasPostRender)
            {
                this.plugins[this._i].postRender();
            }
        }
    },

    /**
     * Clear down this PluginManager, calls destroy on every plugin and nulls out references.
     *
     * @method Phaser.PluginManager#destroy
     */
    destroy: function ()
    {
        this.removeAll();

        this.game = null;
    }

};

Phaser.PluginManager.prototype.constructor = Phaser.PluginManager;
